﻿using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;
using System.Threading;
using System.Runtime;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;

using PickGold;
using PickGold.Api;
using PickGold.Util;
using PickGold.Handler;
using PickGold.Arguments;
using PickGold.Collections;

namespace PickGold.Util.OCR
{
	/// <summary>
	/// 
	/// </summary>
	[Serializable]
	[ComVisible(true)]
	[ClassInterface(ClassInterfaceType.AutoDispatch)]
	public class ImageModel : IDisposable
	{
		public static PixelFormat DefaultFormat;
		public static event HEventHandler<string> ModelNotFound;

		[NonSerialized]
		private static readonly object _SyncRoot = new object();
		private static List<ImageModel> _ModelList;

		[NonSerialized]
		private BitmapData _BitmapData;
		[NonSerialized]
		private Bitmap _Bitmap;
		[NonSerialized]
		private Bitmap _Original;
		private object _OriginalSerialized;
		private bool _IsFull;
		[NonSerialized]
		private ImageModelArea[] _ModelAreas;

		/// <summary>
		/// 键
		/// </summary>
		public string Key;
		/// <summary>
		/// 搜索区域
		/// </summary>
		public Rectangle ModelArea;
		/// <summary>
		/// 像素模式
		/// </summary>
		public PixelFormat ModelFormat;
		/// <summary>
		/// 搜索子区域
		/// </summary>
		public ImageModelArea[][] ModelAreas;

		public ImageModel(Bitmap bitmap) : this(bitmap, PixelFormat.DontCare) { }
		public ImageModel(string path) : this(path, PixelFormat.DontCare) { }
		public ImageModel(Bitmap bitmap, PixelFormat format)
		{
			this._Original = bitmap;
			this._OriginalSerialized = bitmap;
			if (!this._Original.RawFormat.Equals(ImageFormat.Bmp))
				throw new ArgumentException("模型必须是 BMP 格式的位图文件。");

			this.ModelFormat = format;
			if (ImageModel.Add(this) < 0)
				throw new ArgumentOutOfRangeException("模型已存在。");

			this.BuildAreas();
		}
		public ImageModel(string path, PixelFormat format)
		{
			var appPath = Path.GetDirectoryName(this.GetType().Assembly.Location);
			if (path[0] == '~')
				this.Key = appPath + path.Substring(1).Replace('/', '\\');
			else
				this.Key = path;
			if (this.Key.IndexOf(Path.DirectorySeparatorChar) >= 0)
				this._Original = new Bitmap(this.Key);
			else
				this._Original = new Bitmap(Utility.GetFile(this.Key));
			if (!this._Original.RawFormat.Equals(ImageFormat.Bmp))
				throw new ArgumentException("模型必须是 BMP 格式的位图文件。");

			this.ModelFormat = format;
			if (ImageModel.Add(this) < 0)
				throw new ArgumentOutOfRangeException("模型已存在。");

			if (this.Key.IndexOf(appPath, StringComparison.OrdinalIgnoreCase) == 0)
				this.Key = this.Key.Replace(appPath, "~").Replace('\\', '/');
			this.BuildAreas();
		}

		public int Length
		{
			get
			{
				if (this._IsFull)
					return 1;

				return this.ModelAreas.Length;
			}
		}

		public bool IsFull
		{
			set
			{
				if (this._IsFull == value)
					return;

				this._IsFull = value;
				if (this._IsFull && this.ModelAreas.Length == 1 && this.ModelAreas[0].Length == 1)
					return;

				this.BuildAreas();
			}
			get
			{
				return this._IsFull;
			}
		}

		public Bitmap Model
		{
			get
			{
				if (this._Original != null)
					return this._Original;

				this._Original = this._OriginalSerialized as Bitmap;
				if (this._Original != null)
				{
					var ms = new MemoryStream();
					this._Original.Save(ms, this._Original.RawFormat);
					this._Original = new Bitmap(ms);
					ms.Close();
					ms.Dispose();
					return this._Original;
				}

				var t = this.Key;
				if (t[0] == '~')
					t = Path.GetDirectoryName(this.GetType().Assembly.Location) + this.Key.Substring(1).Replace('/', '\\');
				if (t.IndexOf(Path.DirectorySeparatorChar) >= 0)
				{
					if (!File.Exists(t))
					{
						if (ImageModel.ModelNotFound == null)
							return null;

						var e = new EventArgs<string>(t);
						e.Tag = this;
						ImageModel.ModelNotFound(this, e);
						if (string.IsNullOrEmpty(e.Value) || e.Value == t)
							return null;

						t = e.Value;
						this.Key = t;
					}

					this._Original = new Bitmap(t);
					return this._Original;
				}

				t = Utility.GetFile(t);
				if (string.IsNullOrEmpty(t))
					return null;

				this._Original = new Bitmap(t);
				return this._Original;
			}
		}

		public ImageModelArea[] this[int v]
		{
			get
			{
				if (this._IsFull && v == 0)
				{
					if (this.ModelAreas.Length == 1 && this.ModelAreas[v].Length == 1)
						return this.ModelAreas[v];

					return new ImageModelArea[] { new ImageModelArea(this) };
				}

				return this.ModelAreas[v];
			}
		}

		public ImageModelArea this[int v, int h]
		{
			get
			{
				if (this._IsFull && v == 0 && h == 0)
				{
					if (this.ModelAreas.Length == 1 && this.ModelAreas[v].Length == 1)
						return this.ModelAreas[v][h];

					return new ImageModelArea(this);
				}

				return this.ModelAreas[v][h];
			}
		}

		public ImageModelArea[][] BuildAreas()
		{
			this._ModelAreas = null;
			if (this._IsFull)
			{
				this.ModelAreas = new ImageModelArea[][] { new ImageModelArea[] { new ImageModelArea(this) } };
				return this.ModelAreas;
			}

			//(byte)(.299 * red + .587 * green + .114 * blue);//RGB -> GDI+:BGR
			int i = 0, x = 0, y = 0, iX = 0, iY = 0, iP = 0, iRowF = 0, iRowT = 0, iColumnF = 0, iColumnT = 0;
			var results = new List<Rectangle>();
			var rec = new Rectangle(0, 0, this.Model.Width, this.Model.Height);
			var bd = this.LockBits();
			var h = this.Model.Height;
			var w = this.Model.Width;
			var iS = bd.Stride;
			var iW = iS / bd.Width;
			var rss = new List<List<ImageModelArea>>();
			unsafe
			{
				//var bb = (byte*)bdb.Scan0.ToPointer();
				var bs = (byte*)bd.Scan0.ToPointer();
				for (y = 0; y <= h; y++)//垂直扫描
				{
					for (x = 0; y < h && x < w; x++)//水平扫描忽略像素 / 扫描水平分界线
					{
						iP = y * iS + x * iW;
						for (i = 0; i < iW; i++)//分色扫描
						{
							if (bs[iP + i] != bs[i])
								break;//与忽略像素分色不匹配，中断扫描像素分色
						}
						if (i < iW)
							break;//与忽略像素不匹配，中断扫描忽略像素
					}
					if (y < h)//非最后一行
					{
						if (x < w)//中断过，表示有非忽略像素
						{
							iRowT++;//有效行加一
							iRowF = 0;//清除无效行数据
							continue;//进入下一行扫描：y++
						}

						iRowF++;//无效行加一
					}
					if (iRowT == 0)
						continue;//如果不存在有效行，进入下一行扫描：y++

					x = 0;//定义有效行中水平块序数
					for (iX = 0; iX <= w; iX++)//水平扫描忽略像素 / 扫描垂直分界线 / 分行后
					{
						for (iY = 0; iX < w && iY < iRowT; iY++)//垂直扫描有效行
						{
							iP = (y - iY - 1) * iS + iX * iW;
							for (i = 0; i < iW; i++)//分色扫描
							{
								if (bs[iP + i] != bs[i])
									break;//与忽略像素分色不匹配，中断扫描像素分色
							}
							if (i < iW)
								break;//与忽略像素不匹配，中断扫描忽略像素
						}
						if (iX < w)//非最后一列
						{
							if (iY < iRowT)//中断过，表示有非忽略像素
							{
								iColumnT++;//有效列加一
								iColumnF = 0;//清除无效列数据
								continue;//进入下一列扫描：iX++
							}

							iColumnF++;//无效列加一
						}
						if (iColumnT == 0)
							continue;//如果不存在有效列，进入下一列扫描：iX++

						rec = new Rectangle(iX - iColumnT/* 当前扫描列减去有效列 */, y - iRowT/* 当前扫描行减去有效行 */, iColumnT/* 有效列 */, iRowT/* 有效行 */);
						if (x == 0)//如果在行中的水平序数为零，则添加一个结果集
							rss.Add(new List<ImageModelArea>());
						rss[rss.Count - 1].Add(new ImageModelArea(this, rec));//向行结果集中加一个块
						x++;//水平序数加一
						iColumnT = 0;//清除有效列数据
					}//for (iX = 0; iX < w; iX++)
					iRowT = 0;//清除有效行数据
				}//for (y = 0; y <= h; y++)
			}
			//if (rss.Count == 0)
			//	rss.Add(new List<CImageModelArea>() { new CImageModelArea(this, 0, 0, this.Model.Width, this.Model.Height) });

			this.ModelAreas = new ImageModelArea[rss.Count][];
			for (i = 0; i < rss.Count; i++)
				this.ModelAreas[i] = rss[i].ToArray();

			rec = new Rectangle(0, 0, 0, 0);
			for (y = 0; y < this.ModelAreas.Length; y++)
			{
				for (x = 0; x < this.ModelAreas[y].Length; x++)
				{
					if (rec.X > this.ModelAreas[y][x].X)
						rec.X = this.ModelAreas[y][x].X;
					if (rec.Y > this.ModelAreas[y][x].Y)
						rec.Y = this.ModelAreas[y][x].Y;
					if (this.ModelAreas[y][x].Right > rec.Right)
						rec.Width = this.ModelAreas[y][x].Right - rec.Left;
					if (this.ModelAreas[y][x].Bottom > rec.Bottom)
						rec.Height = this.ModelAreas[y][x].Bottom - rec.Top;
				}
			}
			this.ModelArea = rec;
			return this.ModelAreas;
		}

		public ImageModelArea[] GetAllItems()
		{
			if (this._ModelAreas != null)
				return this._ModelAreas;

			if (this._IsFull)
			{
				if (this.ModelAreas.Length == 1 && this.ModelAreas[0].Length == 1)
					this._ModelAreas = this.ModelAreas[0];
				else
					this._ModelAreas = new ImageModelArea[] { new ImageModelArea(this) };
			}
			else
			{
				var ls = new List<ImageModelArea>();
				for (var i = 0; i < this.ModelAreas.Length; i++)
					ls.AddRange(this.ModelAreas[i]);
				this._ModelAreas = ls.ToArray();
			}
			return this._ModelAreas;
		}

		public ImageModelArea GetItem(int index)
		{
			if (this._ModelAreas == null)
				this.GetAllItems();

			return this._ModelAreas[index];
		}

		public BitmapData LockBits()
		{
			if (this._BitmapData != null)
				return this._BitmapData;

			if (this.Model == null)
				return null;

			var ms = new MemoryStream();
			this.Model.Save(ms, this.Model.RawFormat);
			this._Bitmap = new Bitmap(ms);
			ms.Close();
			ms.Dispose();
			if (this.ModelFormat == PixelFormat.DontCare)
				this.ModelFormat = this._Bitmap.PixelFormat;
			if (this.ModelFormat == PixelFormat.DontCare)
				this.ModelFormat = ImageModel.DefaultFormat;
			if (this.ModelFormat == PixelFormat.DontCare)
				this.ModelFormat = PixelFormat.Format32bppArgb;
			var rec = new Rectangle(0, 0, this._Bitmap.Width, this._Bitmap.Height);
			this._BitmapData = this._Bitmap.LockBits(rec, ImageLockMode.ReadOnly, this.ModelFormat);
			return this._BitmapData;
		}

		public void UnlockBits()
		{
			if (this._BitmapData == null || this._Bitmap == null)
				return;

			try
			{
				this._Bitmap.UnlockBits(this._BitmapData);
			}
			catch { }
			this._Bitmap.Dispose();
			this._Bitmap = null;
			this._BitmapData = null;
		}

		public static ImageModel GetModel(int index)
		{
			if (ImageModel._ModelList == null)
				return null;

			if (!Monitor.TryEnter(ImageModel._SyncRoot, Fiber.LockTimeout))
				Fiber.ThrowLockTimeoutException(ImageModel._SyncRoot);
			try
			{
				if (index < 0 || index >= ImageModel._ModelList.Count)
					return null;

				return ImageModel._ModelList[index];
			}
			finally
			{
				Monitor.Exit(ImageModel._SyncRoot);
			}
		}

		public static ImageModel GetModel(string key)
		{
			return ImageModel.GetModel(key, PixelFormat.DontCare);
		}
		public static ImageModel GetModel(string key, PixelFormat format)
		{
			if (ImageModel._ModelList == null)
				return null;

			var i = 0;
			var e = format;
			if (e == PixelFormat.DontCare)
				e = ImageModel.DefaultFormat;
			if (e == PixelFormat.DontCare)
				e = PixelFormat.Format32bppArgb;
			if (!Monitor.TryEnter(ImageModel._SyncRoot, Fiber.LockTimeout))
				Fiber.ThrowLockTimeoutException(ImageModel._SyncRoot);
			try
			{
				//if (format == PixelFormat.DontCare)
				//{
				//    foreach (var im in CImageModel._ModelList)
				//    {
				//        if (string.Compare(key, im.Key) == 0)
				//            i++;
				//    }
				//    if (i == 0)
				//        return null;

				//    if (i == 1)
				//    {
				//        foreach (var im in CImageModel._ModelList)
				//        {
				//            if (string.Compare(key, im.Key) == 0)
				//                return im;
				//        }
				//    }
				//}
				foreach (var im in ImageModel._ModelList)
				{
					if (im.ModelFormat == format && string.Compare(key, im.Key) == 0)
						return im;
				}
				//if (i > 1)
				//{
				//    foreach (var im in CImageModel._ModelList)
				//    {
				//        if (string.Compare(key, im.Key) == 0)
				//            return im;
				//    }
				//}
			}
			finally
			{
				Monitor.Exit(ImageModel._SyncRoot);
			}
			return null;
		}

		public static ImageModel GetModel(Bitmap bitmap)
		{
			return ImageModel.GetModel(bitmap, PixelFormat.DontCare);
		}
		public static ImageModel GetModel(Bitmap bitmap, PixelFormat format)
		{
			if (ImageModel._ModelList == null)
				return null;

			if (format == PixelFormat.DontCare)
				format = ImageModel.DefaultFormat;
			if (format == PixelFormat.DontCare)
				format = PixelFormat.Format32bppArgb;
			if (!Monitor.TryEnter(ImageModel._SyncRoot, Fiber.LockTimeout))
				Fiber.ThrowLockTimeoutException(ImageModel._SyncRoot);
			try
			{
				foreach (var im in ImageModel._ModelList)
				{
					if (im.ModelFormat == format && im.Model == bitmap)
						return im;
				}
			}
			finally
			{
				Monitor.Exit(ImageModel._SyncRoot);
			}
			return null;
		}

		public static int Add(ImageModel model)
		{
			if (ImageModel._ModelList == null)
				ImageModel._ModelList = new List<ImageModel>();
			if (!Monitor.TryEnter(ImageModel._SyncRoot, Fiber.LockTimeout))
				Fiber.ThrowLockTimeoutException(ImageModel._SyncRoot);
			try
			{
				if (ImageModel._ModelList.Contains(model))
					return -1;

				if (ImageModel.DefaultFormat == PixelFormat.DontCare)
					ImageModel.DefaultFormat = model.ModelFormat;
				ImageModel._ModelList.Add(model);
				return ImageModel._ModelList.Count - 1;
			}
			finally
			{
				Monitor.Exit(ImageModel._SyncRoot);
			}
		}

		public static bool Remove(ImageModel model)
		{
			if (ImageModel._ModelList == null || model == null)
				return false;

			if (!Monitor.TryEnter(ImageModel._SyncRoot, Fiber.LockTimeout))
				Fiber.ThrowLockTimeoutException(ImageModel._SyncRoot);
			try
			{
				if (!ImageModel._ModelList.Contains(model))
					return false;

				ImageModel._ModelList.Remove(model);
			}
			finally
			{
				Monitor.Exit(ImageModel._SyncRoot);
			}
			model.Dispose();
			return true;
		}

		public static ImageModel[] GetModelArray()
		{
			if (ImageModel._ModelList == null)
				return new ImageModel[] { };

			return ImageModel._ModelList.ToArray();
		}

		#region IDisposable 成员

		public void Dispose()
		{
			//throw new NotImplementedException();
			if (ImageModel._ModelList != null)
			{
				if (!Monitor.TryEnter(ImageModel._SyncRoot, Fiber.LockTimeout))
					Fiber.ThrowLockTimeoutException(ImageModel._SyncRoot);
				try
				{
					ImageModel._ModelList.Remove(this);
				}
				finally
				{
					Monitor.Exit(ImageModel._SyncRoot);
				}
			}
			if (this._BitmapData == null)
				return;

			try
			{
				this.Model.UnlockBits(this._BitmapData);
			}
			catch { }
			this._BitmapData = null;
		}

		~ImageModel()
		{
			this.Dispose();
		}

		#endregion

		public Bitmap GetAreaBitmap()
		{
			var bmp = this.Model.Clone() as Bitmap;
			//var color = bmp.GetPixel(0, 0);
			//color = Color.FromArgb(byte.MaxValue - color.A, byte.MaxValue - color.R, byte.MaxValue - color.G, byte.MaxValue - color.B);
			//var pen = new Pen(color, 1);
			//var gps = Graphics.FromImage(bmp);
			//for (int y = 0; y < this.ModelAreas.Length; y++)
			//{
			//    for (int x = 0; x < this.ModelAreas[y].Length; x++)
			//        gps.DrawRectangle(pen, this.ModelAreas[y][x].Area);
			//}
			//gps.Dispose();
			return bmp;
		}

		public override string ToString()
		{
			return this.ModelArea + ";" + this.ModelFormat + ";" + this.Key;
			//return base.ToString();
		}
	}
}
